{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "92d19e69",
   "metadata": {},
   "source": [
    "| [02_advanced/06_装饰器.ipynb](https://github.com/shibing624/python-tutorial/blob/master/02_advanced/06_装饰器.ipynb)  | Decorator装饰器  |[Open In Colab](https://colab.research.google.com/github/shibing624/python-tutorial/blob/master/02_advanced/06_装饰器.ipynb) |\n",
    "\n",
    "# 装饰器：Decorator\n",
    "如果你有一批变量想统一按一个规则处理，并且需要缩减代码，你需要函数。\n",
    "\n",
    "如果你有一批函数想统一按一个规则处理，并且需要缩减代码，你需要装饰器（Decorator）\n",
    "\n",
    "理清下面2点：\n",
    "\n",
    "函数\n",
    "- 接受参数\n",
    "- 做点事情\n",
    "- 返回结果\n",
    "\n",
    "\n",
    "装饰器\n",
    "- 接受函数作为参数\n",
    "- 做点事情\n",
    "- 返回一个函数\n",
    "\n",
    "\n",
    "用 @ 来使用装饰器\n",
    "\n",
    "使用 @ 符号来将某个函数替换为装饰符之后的函数：\n",
    "\n",
    "例如这个函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "ab40e597",
   "metadata": {},
   "outputs": [],
   "source": [
    "def dec(f):\n",
    "    print('I am decorating function', id(f))\n",
    "    return f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "2d5dc64a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def foo(x):\n",
    "    print(x)  # I am decorating function 45206384"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "02d198a1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I am decorating function 140410365416640\n"
     ]
    }
   ],
   "source": [
    "foo = dec(foo)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e1b3e56",
   "metadata": {},
   "source": [
    "可以替换为："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4d903f7d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I am decorating function 140410366082496\n"
     ]
    }
   ],
   "source": [
    "@dec\n",
    "def foo(x):\n",
    "    print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70d1d89d",
   "metadata": {},
   "source": [
    "### 例子\n",
    "定义两个装饰器函数，一个将原来的函数值加一，另一个乘二："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "1107d480",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plus_one(f):\n",
    "    def new_func(x):\n",
    "        return f(x) + 1\n",
    "\n",
    "    return new_func"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "c091bc23",
   "metadata": {},
   "outputs": [],
   "source": [
    "def times_two(f):\n",
    "    def new_func(x):\n",
    "        return f(x) * 2\n",
    "\n",
    "    return new_func"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8625804c",
   "metadata": {},
   "source": [
    "定义函数，先乘二再加一："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "a84188e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "@plus_one\n",
    "@times_two\n",
    "def foo(x):\n",
    "    return int(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "ea5f6fd0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b = foo(2)\n",
    "b  # 5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f1bf966",
   "metadata": {},
   "source": [
    "## 修饰器工厂\n",
    "decorators factories 是返回修饰器的函数\n",
    "\n",
    "它的作用在于产生一个可以接受参数的修饰器，\n",
    "\n",
    "例如我们想将 函数 输出的内容写入一个文件去，可以这样做："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "befb9986",
   "metadata": {},
   "outputs": [],
   "source": [
    "def super_loud(filename):\n",
    "    fp = open(filename, 'w')\n",
    "\n",
    "    def loud(f):\n",
    "        def new_func(*args, **kw):\n",
    "            fp.write(str(args))\n",
    "            fp.writelines('\\n')\n",
    "            fp.write('calling with' + str(args) + str(kw))\n",
    "            # 确保内容被写入\n",
    "            fp.flush()\n",
    "            fp.close()\n",
    "            rtn = f(*args, **kw)\n",
    "            return rtn\n",
    "\n",
    "        return new_func\n",
    "\n",
    "    return loud"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "4fd6baa5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100\n"
     ]
    }
   ],
   "source": [
    "@super_loud('test.txt')\n",
    "def foo(x):\n",
    "    print(x)\n",
    "\n",
    "\n",
    "# 调用 foo 就会在文件中写入内容：\n",
    "foo(100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "f43e0965",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "os.remove('test.txt')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a54aee1f",
   "metadata": {},
   "source": [
    "## @classmethod 装饰器\n",
    "在 Python 标准库中，有很多自带的装饰器，\n",
    "\n",
    "例如 classmethod 将一个对象方法转换了类方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "37f2970d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Foo(object):\n",
    "    @classmethod\n",
    "    def bar(cls, x):\n",
    "        print('the input is', x)\n",
    "\n",
    "    def __init__(self):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b6dca6f",
   "metadata": {},
   "source": [
    "类方法可以通过 类名.方法 来调用："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "150420db",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "the input is 10\n"
     ]
    }
   ],
   "source": [
    "Foo.bar(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adba89fd",
   "metadata": {},
   "source": [
    "## @property 装饰器\n",
    "有时候，我们希望像 Java 一样支持 getters 和 setters 的方法，\n",
    "\n",
    "这时候就可以使用 property 装饰器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "a7d0c3dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Foo(object):\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    @property\n",
    "    def x(self):\n",
    "        return self.data\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae91216c",
   "metadata": {},
   "source": [
    "此时可以使用 .x 这个属性查看数据（不需要加上括号）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "2017109a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "22\n"
     ]
    }
   ],
   "source": [
    "foo = Foo(22)\n",
    "print(foo.x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27b3c443",
   "metadata": {},
   "source": [
    "这样做的好处在于，这个属性是只读的：\n",
    "\n",
    "foo.x = 1 会报错\n",
    "\n",
    "\n",
    "如果想让它变成可读写，可以加上一个装饰符 @x.setter："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "d3776ec2",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Foo(object):\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    @property\n",
    "    def x(self):\n",
    "        return self.data\n",
    "\n",
    "    @x.setter\n",
    "    def x(self, value):\n",
    "        self.data = value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "45c72448",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1000"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "foo = Foo(1000)\n",
    "foo.x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "c238a25e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2222"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "foo.x = 2222\n",
    "foo.x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b06a6e0",
   "metadata": {},
   "source": [
    "## 应用：定时器\n",
    "\n",
    "要求：写一个定时器功能，要求监控一个执行程序，超时则报警。\n",
    "\n",
    "如何完成？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "db796406",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "import signal\n",
    "import time\n",
    "\n",
    "\n",
    "def set_timeout(num, callback):\n",
    "    def wrap(func):\n",
    "        def handle(signum, frame):  # 收到信号 SIGALRM 后的回调函数，参数1是信号的数字，参数2是the interrupted stack frame.\n",
    "            raise RuntimeError\n",
    "\n",
    "        def to_do(*args, **kwargs):\n",
    "            try:\n",
    "                signal.signal(signal.SIGALRM, handle)  # 设置信号和回调函数\n",
    "                signal.alarm(num)  # 设置 num 秒的闹钟\n",
    "                print('start alarm signal.')\n",
    "                r = func(*args, **kwargs)\n",
    "                print('close alarm signal.')\n",
    "                signal.alarm(0)  # 关闭闹钟\n",
    "                return r\n",
    "            except RuntimeError as e:\n",
    "                callback()\n",
    "\n",
    "        return to_do\n",
    "\n",
    "    return wrap\n",
    "\n",
    "\n",
    "def after_timeout():  # 超时后的处理函数\n",
    "    print(\"do something after timeout.\")\n",
    "    raise RuntimeError\n",
    "\n",
    "\n",
    "@set_timeout(2, after_timeout)  # 限时 2 秒超时\n",
    "def connect():  # 要执行的函数\n",
    "    time.sleep(2.4)  # 函数执行时间，写大于2的值，可测试超时\n",
    "    return \"完成\"\n",
    "\n",
    "class Demo:\n",
    "    @set_timeout(2, after_timeout)\n",
    "    def conn(self):\n",
    "        time.sleep(3)\n",
    "        return \"ok\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2de7ccd",
   "metadata": {},
   "source": [
    "试一下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "b48a9f03",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start alarm signal.\n",
      "do something after timeout.\n",
      "err\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    a = connect()\n",
    "    print(a)\n",
    "except Exception as e:\n",
    "    a = 'err'\n",
    "    print(a)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "d77a4854",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start alarm signal.\n",
      "close alarm signal.\n",
      "ok\n"
     ]
    }
   ],
   "source": [
    "b = Demo()\n",
    "try:\n",
    "    c = b.conn()\n",
    "    print(c)\n",
    "except RuntimeError as e:\n",
    "    print('run time err.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "191f8a51",
   "metadata": {},
   "source": [
    "如果不超时："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "9a8c240b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "start alarm signal.\n",
      "close alarm signal.\n",
      "ok\n"
     ]
    }
   ],
   "source": [
    "class Demo:\n",
    "    @set_timeout(2, after_timeout)\n",
    "    def conn(self):\n",
    "        time.sleep(1)\n",
    "        return \"ok\"\n",
    "    \n",
    "b = Demo()\n",
    "try:\n",
    "    c = b.conn()\n",
    "    print(c)\n",
    "except RuntimeError as e:\n",
    "    print('run time err.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91f17f28",
   "metadata": {},
   "source": [
    "本节完。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "041101e8",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}